

#include <iostream>
#include <fstream>
#include <sstream>
#include <stdio.h>
#include "common/log.h"

#include "src/opencl_runtime.h"
#include "cl_common/cl_memory_pool.h"

#include "src/opengl_runtime.h"
#include "src/gl_common/gl_memory_pool.h"

namespace
{
    const int ARRAY_SIZE = 10;
    const char *FILE_NAME = "/data/local/tmp/opencl_demo/main.cl";
    const char *KERNEL_NAME = "test_kernel";
} // namespace

void PrintImage2DData(float *data, int w, int h)
{
    for (int i = 0; i < h; i++)
    {
        for (int j = 0; j < w; j++)
        {
            for (int k = 0; k < 4; k++)
            {
                std::cout << data[8 * i + 8 * j + k] << " ";
            }
            std::cout << "    ";
        }
        std::cout << std::endl;
    }
    std::cout << "data print finish!" << std::endl;
}

int main(int argc, char **argv)
{
    PNN_LOG(INFO) << "**************** OpenGL Program Start ****************";

    PanNN::OpenGL::OpenGLRuntime glRuntime;
    glRuntime.Init();

    // hwc: 8 * 8 * 4
    float glInput1[256];
    for (int i = 0; i < 256; i++)
    {
        glInput1[i] = 123;
    }

    PanNN::OpenGL::GLMemeoryPool glMemPool;
    auto glImageID1 = glMemPool.CopyHostToDeviceTexture(glInput1, 8, 8, 4);
    float *hostptr = (float *)glMemPool.CopyDeviceTextureToHost(glImageID1);
    PrintImage2DData(hostptr, 8, 8);

    PNN_LOG(INFO) << "**************** OpenCL Program Start ****************";

    auto m_openCLRt = std::make_unique<PanNN::OpenCL::OpenCLRuntime>();
    m_openCLRt->Init();

    PanNN::OpenCL::CLMemeoryPool *m_clMemoryPool = new PanNN::OpenCL::CLMemeoryPool(m_openCLRt.get());

    // 2. Build Kernel
    const char *FILE_NAME = "/data/local/tmp/opencl_demo/main.cl";
    const char *KERNEL_NAME = "ElementAdd";
    cl_int errNum = CL_SUCCESS;

    // 2.1 Get Program Source
    std::ifstream kernelFile(FILE_NAME, std::ios::in);
    std::ostringstream oss;
    oss << kernelFile.rdbuf();
    std::string srcStdStr = oss.str();
    const char *srcStr = srcStdStr.c_str();

    // 2.2 Create Program
    std::string buildOption = "";
    auto m_program = new (std::nothrow) cl::Program(*m_openCLRt->m_context, srcStr, false, &errNum);
    errNum = m_program->build({*m_openCLRt->m_device}, buildOption.c_str()); // compile Program
    if (errNum != CL_SUCCESS)
    {
        if (m_program->getBuildInfo<CL_PROGRAM_BUILD_STATUS>(*m_openCLRt->m_device) == CL_BUILD_ERROR)
        {
            std::string buildLog = m_program->getBuildInfo<CL_PROGRAM_BUILD_LOG>(*m_openCLRt->m_device);
            PNN_LOG(ERROR) << "Program build log: " << buildLog;
        }
        PNN_LOG(ERROR) << "Build program failed: ret = " << errNum;
        return CL_RUNTIME_PROGRAM_INIT_ERR;
    }
    auto m_kernel = new (std::nothrow) cl::Kernel(*m_program, KERNEL_NAME);

    // 3. Bind Data
    const int ARRAY_SIZE = 8 * 8 * 4;
    float *clInput1 = (float *)malloc(sizeof(float) * ARRAY_SIZE);
    float *clInput2 = (float *)malloc(sizeof(float) * ARRAY_SIZE);
    for (int i = 0; i < ARRAY_SIZE; i++)
    {
        clInput1[i] = (float)123;
        clInput2[i] = (float)566;
    }

#if defined(SHARING_MEM_WITH_OPENGL) && defined(CL_HPP_TARGET_OPENCL_VERSION) && (CL_HPP_TARGET_OPENCL_VERSION >= 120)
    PNN_LOG(INFO) << "Use OpenGL Image Data";
    auto image_in1 = new cl::ImageGL(*m_openCLRt->m_context, CL_MEM_READ_WRITE, GL_TEXTURE_2D, 0, glImageID1, nullptr);
#else
    PNN_LOG(INFO) << "new OpenCL Image Data";
    auto image_in1 = m_clMemoryPool->CLCreateImage(8, 8, 4, CL_MEM_READ_WRITE, clInput1);
#endif
    auto image_in2 = m_clMemoryPool->CLCreateImage(8, 8, 4, CL_MEM_READ_WRITE, clInput2);
    auto image_out = m_clMemoryPool->CLCreateImage(8, 8, 4, CL_MEM_READ_WRITE);
    cl_int2 outShape = {8, 8};
    float actMin = {-1000.0};
    float actMax = {1000.0};

    errNum = m_kernel->setArg(0, sizeof(cl_mem), image_in1);
    errNum |= m_kernel->setArg(1, sizeof(cl_mem), image_in2);
    errNum |= m_kernel->setArg(2, sizeof(cl_mem), image_out);
    errNum |= m_kernel->setArg(3, outShape);
    errNum |= m_kernel->setArg(4, actMin);
    errNum |= m_kernel->setArg(5, actMax);

    if (errNum != CL_SUCCESS)
    {
        PNN_LOG(ERROR) << "Data Bind Failed, ret = " << m_openCLRt->CLErrorCode(errNum);
        return CL_RUNTIME_PROGRAM_INIT_ERR;
    }

    // 4. Run
    size_t globalWorkSize[2] = {8, 8};
    size_t localWorkSize[1] = {1};

    errNum = m_openCLRt->m_defaultCmdQueue->enqueueNDRangeKernel(*m_kernel, cl::NDRange(0),
                                                                 cl::NDRange(globalWorkSize[0], globalWorkSize[1]),
                                                                 cl::NDRange(localWorkSize[0]), NULL, NULL);

    // 5. Get Output Data
    float result[ARRAY_SIZE] = {0.0};
    std::vector<size_t> region{8, 8, 1};
    m_clMemoryPool->CopyDeviceImageToHost(result, image_out, region, ARRAY_SIZE * sizeof(float));
    PrintImage2DData(result, 8, 8);

    return 0;
}
